package src;

import jargs.gnu.CmdLineParser.IllegalOptionValueException;
import jargs.gnu.CmdLineParser.UnknownOptionException;

import java.io.IOException;
import java.net.SocketTimeoutException;

import lib.ArgumentValidator;
import lib.Command;
import lib.CommandParser;
import lib.logging.ANSIConsoleLogger;
import lib.logging.ILogger;
import lib.messaging.NetworkAddress;
import lib.rpc.ClientRPCFunctionality;
import lib.rpc.ClientRPCMethods;

/**
 * Client for sending commands (CreateUser, UpdateWall, etc) to server
 *
 * @author abhip
 *
 */
public class Client
{

    private static boolean IsExitCommand(Command command)
    {
        if (command.CommandName().equalsIgnoreCase(ClientConfiguration.ExitCommandName))
        {
            return true;
        }

        return false;
    }

    /**
     * Main method for Client
     *
     * @param args
     * @throws UnknownOptionException
     * @throws IllegalOptionValueException
     */
    public static void main(String[] args) throws IllegalOptionValueException, UnknownOptionException
    {
        ClientConfiguration clientConfig = ClientConfiguration.ReadConfiguration(args);
        Client client = new Client(clientConfig);
        client.PrintHelpMenu();

        while (true) {
            client.consoleLogger.LogInfo(
                    "%s Enter Command (%s to exit)",
                    ClientConfiguration.ClientPrompt,
                    ClientConfiguration.ExitCommandName);
            String commandString = System.console().readLine();

            if (commandString == null || commandString.equals("")) {
                client.PrintHelpMenu();
                continue;
            }

            try
            {
                Command command = CommandParser.ParseCommand(commandString);
                if (IsExitCommand(command))
                {
                    break;
                }
                client.HandleCommand(command);
            }
            catch (SocketTimeoutException e)
            {
                client.consoleLogger.LogError(
                        "Network connection timed out! "
                                + "Command '%s' may or may not have executed on server.",
                        commandString);
            }
            catch (IllegalArgumentException e)
            {
                client.logger.LogException(e);
                client.PrintHelpMenu();
            }
            catch (IOException e)
            {
                client.logger.LogException(e);
            }
            catch (ClassNotFoundException e)
            {
                client.logger.LogException(e);
            }
        }
    }


    private IClientCommandHandler clientCommandHandler;
    private ILogger consoleLogger;
    private ILogger logger;

    public Client(ClientConfiguration clientConfig)
    {
        ArgumentValidator.IsNotNull("clientConfig", clientConfig);

        this.logger = new ANSIConsoleLogger();
        this.consoleLogger = new ANSIConsoleLogger();
        this.clientCommandHandler = new ClientCommandHandler(
                clientConfig.ClientAddress(),
                new ClientRPCMethods(
                        logger,
                        new ClientRPCFunctionality(
                                logger,
                                ClientConfiguration.Timeout,
                                clientConfig.ClientPacketDropProbability())),
                ClientConfiguration.UserFileSuffix);
    }

    private void HandleCommand(Command command) throws ClassNotFoundException, IOException
    {
        if (command.CommandName().equalsIgnoreCase(IClientCommandHandler.CreateUserCommandName))
        {
            Object args[] = command.CommandArgs();
            if (args == null || args.length != 3)
            {
                throw new IllegalArgumentException(
                        String.format(
                                "Incorrect syntax for '%s' command. Expected number of arguments: %d, Actual: %d",
                                IClientCommandHandler.CreateUserCommandName,
                                3,
                                (args == null) ? 0 : args.length));
            }

            int serverPort = Integer.parseInt((String) args[0]);
            NetworkAddress serverAddress = new NetworkAddress(ClientConfiguration.ServerName, serverPort);
            int roundID = Integer.parseInt((String) args[1]);
            String userName = (String) args[2];

            logger.LogInfo("Executing command '%s' on server...", command.toString());
            clientCommandHandler.HandleCreateUserCommand(serverAddress, roundID, userName);
            return;
        }

        if ((command.CommandName().equalsIgnoreCase(IClientCommandHandler.UpdateCommandName)))
        {
            Object args[] = command.CommandArgs();

            if (args == null || args.length < 4)
            {
                throw new IllegalArgumentException(
                        String.format(
                                "Incorrect syntax for '%s' command. Expected number of arguments: %d, Actual: %d",
                                IClientCommandHandler.UpdateCommandName,
                                4,
                                (args == null) ? 0 : args.length));
            }

            int serverPort = Integer.parseInt((String) args[0]);
            NetworkAddress serverAddress = new NetworkAddress(ClientConfiguration.ServerName, serverPort);
            int roundID = Integer.parseInt((String) args[1]);
            String userName = (String) args[2];

            // coalesce the rest of tokens together to form one string
            // containing data
            StringBuilder sb = new StringBuilder();
            for (int i = 3; i < args.length; ++i) {
                sb.append(args[i]);
                sb.append(" ");
            }
            String data = sb.toString();

            logger.LogInfo("Executing command '%s' on server...", command.toString());
            clientCommandHandler.HandleUpdateCommand(serverAddress, roundID, userName, data);
            return;
        }

        if ((command.CommandName().equalsIgnoreCase(IClientCommandHandler.FetchCommandName)))
        {
            Object args[] = command.CommandArgs();
            if (args == null || args.length != 4)
            {
                throw new IllegalArgumentException(
                        String.format(
                                "Incorrect syntax for '%s' command. Expected number of arguments: %d, Actual: %d",
                                IClientCommandHandler.FetchCommandName,
                                4,
                                (args == null) ? 0 : args.length));
            }

            int serverPort = Integer.parseInt((String) args[0]);
            NetworkAddress serverAddress = new NetworkAddress(ClientConfiguration.ServerName, serverPort);
            int roundID = Integer.parseInt((String) args[1]);
            String userName = (String) args[2];
            int postNumber = Integer.parseInt((String) args[3]);

            logger.LogInfo("Executing command '%s' on server...", command.toString());
            String result = clientCommandHandler.HandleFetchCommand(serverAddress, roundID, userName, postNumber);

            if (result == null)
            {
                consoleLogger.LogError("%s failed! Please retry...'",command.toString(), result);
                return;
            }

            consoleLogger.LogInfo("%s suceeded! RESULT: '%s'",command.toString(), result);
            return;
        }

        throw new IllegalArgumentException(
                String.format(
                        "unknown command!: %s",
                        command.toString()));
    }

    private void PrintHelpMenu() {
        consoleLogger.LogInfo("Help Menu:");
        consoleLogger.LogInfo("%s <server port> <roundID> <userName>", IClientCommandHandler.CreateUserCommandName);
        consoleLogger.LogInfo("%s <server port> <roundID> <userName> <message to post>", IClientCommandHandler.UpdateCommandName);
        consoleLogger.LogInfo("%s <server port> <roundID> <userName> <post number>", IClientCommandHandler.FetchCommandName);
        consoleLogger.LogInfo(""); // printing new line on console
    }
}
